package me.yricky.mayu.utils.misc;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

/**
 * @author Yricky
 * @date 2022/4/21
 */
public final class FileIntMatrix implements IIntMatrix {
    private final RandomAccessFile file;
    private final int _row, _col;
    private long rCIndex = -1L;
    private long wCIndex = -1L;
    private int dirtyWCIndex = 0;
    private ByteBuffer _wCache;
    private IntBuffer rCache;
    private IntBuffer wCache;

    private static final long CACHE_INDEX_MASK = 0xffff_ffff_ffff_0000L;
    private static final long INDEX_MASK = ~CACHE_INDEX_MASK;
    private static final int CACHE_SIZE  = ((int)INDEX_MASK + 1) << 2 ;

    private FileIntMatrix(RandomAccessFile _file) throws IOException {
        file = _file;

        _row = _get(0);
        _col = _get(1);
    }

    @Override
    public int getRow() {
        return _row;
    }

    @Override
    public int getCol() {
        return _col;
    }

    @Override
    public int get(int row,int col,int def){
        if(row < 0 || row >= _row || col < 0 || col >= _col){
            return def;
        }
        try {
            return  _get( ((long)row * _col + col) + 2);
        }catch (IOException ignored){
            return def;
        }
    }

    @Override
    public void set(int row,int col,int value){
        if(row < 0 || row >= _row || col < 0 || col >= _col){
            return;
        }
        try {
            _set( ((long)row * _col + col) + 2,value);
        }catch (IOException ignored){

        }
    }



    public void save(){
        try {
            _set((long)_row*_col + 2,0xffffffff);
            saveWCacheToFile();
        }catch (Exception ignored){ }
        wCIndex = -1;
        wCache = null;
        _wCache = null;
    }

    private int _get(long intIndex) throws IOException {
        long index1 = intIndex & CACHE_INDEX_MASK;
        long index2 = intIndex & INDEX_MASK;
        if(index1 == wCIndex){
            return wCache.get((int) index2);
        }
        if(index1 != rCIndex){
            loadRCacheFromFile(index1);
        }
        return rCache.get((int) index2);
    }

    private void  _set(long intIndex,int value) throws IOException {
        long index1 = intIndex & CACHE_INDEX_MASK;
        long index2 = intIndex & INDEX_MASK;
        if(index1 != wCIndex){
            swapWCacheTo(index1);
        }
        dirtyWCIndex = Math.max(dirtyWCIndex,(int)index2);
        wCache.put((int) index2,value);
    }

    private void swapWCacheTo(long newWCIndex) throws IOException{
        saveWCacheToFile();
        wCIndex = newWCIndex;
        file.seek(wCIndex * 4);
        byte[] ba = new byte[CACHE_SIZE];
        file.read(ba);
        _wCache = ByteBuffer.wrap(ba);
        wCache = _wCache.asIntBuffer();
    }

    private void saveWCacheToFile() throws IOException {
        if(wCIndex >= 0 && _wCache != null){
            file.seek(wCIndex * 4);
            file.write(_wCache.array(),0,dirtyWCIndex * 4 + 4);
            dirtyWCIndex = 0;
        }
        if(rCIndex == wCIndex){
            rCache = wCache;
        }
    }

    private void loadRCacheFromFile(long rCIndex) throws IOException{
        this.rCIndex = rCIndex;
        file.seek(rCIndex * 4);
        byte[] ba = new byte[CACHE_SIZE];
        file.read(ba);
        ByteBuffer _rCache = ByteBuffer.wrap(ba);
        rCache = _rCache.asIntBuffer();
    }

    @Nullable
    public static FileIntMatrix create(int row,int col,File file){
        if((file.isFile() && file.canRead() && file.canWrite()) || !file.exists()){
            try {
                if(!file.exists()){
                    file.createNewFile();
                }
                RandomAccessFile f = new RandomAccessFile(file,"rw");
                f.seek(0);
                f.writeInt(row);
                f.seek(4);
                f.writeInt(col);
                return new FileIntMatrix(f);
            }catch (IOException e){
                return null;
            }
        }else{
            return null;
        }
    }

    @Nullable
    public static FileIntMatrix load(File file){
        if(file.isFile() && file.canRead() && file.canWrite()){
            try {
                RandomAccessFile f = new RandomAccessFile(file,"rw");
                return new FileIntMatrix(f);
            }catch (IOException e){
                return null;
            }
        }else{
            return null;
        }
    }
}
